2023 NepCTF
周末去 NepCTF 玩了下,原本还想尝试一下其他方向的题目的,结果还是只会做密码学方向的题。太菜啦!!!不过这场比赛是个人赛,做完题目后看了一下榜,发现只要单方向 ak 了的话排名都不差,说明各个方向的题目难度设计和题量是比较合理的,真不戳~
![image-20230814130017620](./2023 NepCTF.assets/image-20230814130017620.png)
random_RSA
1 | from gmpy2 import next_prime, invert as inverse_mod |
题目内容是比较简单的,基于 RSA 的变体,这里的 $n$ 是 2048 比特的,$d$ 只有 992 比特,$e \cdot d \equiv 1 \pmod{(p^2-1)\cdot (q^2-1)}$ ,手里头刚好有一篇论文 《Cryptanalysis of RSA Variants with Primes Sharing Most Significant Bits》不过这里还要求 $|p-q|$ 比较小。但是论文里头提了一嘴
![image-20230814094241576](./2023 NepCTF.assets/image-20230814094241576.png)
所以我们直接找 $\frac{e}{N^2-\frac{9}{4}N +1}$ 的连分数就能恢复 $d$,从而恢复 $p,q$ 了。注意到这里的 $p,q,d$ 的高 992 位都是由 getrandbits 生成的随机数。一共 8 次交互机会,所以能有 $992/327+960/32=644$ 个 32 字节的随机数,思路来了:恢复所有的 $p,q,d$ ,恢复一个 MT19937 的状态,然后直接生成后续的 *p,q,d** 就能解密了。
需要注意的是我们虽然能够计算出 $p,q$ ,但不能保证 $p$ 就是原来的 $p$,可能顺序反了,因此还需要组合一下。
这里首先通过交互,前 7 次拿公钥对,最后一次拿密文。然后连分数来一下子
1 | #! sagemath |
然后调一下 MT19937 的板子
1 | #! python3 |
bombe-crib
题目说明:
面对每天六点德军铺天盖地的天气预报,你突然想到了怎么确定关键信息的位置。
注:enigma机可使用pyenigma项目,但源项目有问题,可修改后使用cyberchef对拍。
是一个恩格玛机相关的题目,代码文件太多,这里只放一下交互的 handle 部分
1 | from pwn import * |
看一下题目流程,
- 先生成了一段随机字符串
other
- 指定一个位置
pos
,在该位置插入已知明文WETTERBERICHT
,组成text
- 指定了恩格玛机加密所需要的转子:
dayrotors
- 对
text
进行二十次加密,每次使用不同的tmpkey
和plugin
所以我们就是需要根据 text
的二十个密文来确定 WETTERBERICHT
所在的位置。
在此前我对恩格玛机也是一点都不了解的,在查相关资料的时候,看见了这么一句话
![image-20230814101223587](./2023 NepCTF.assets/image-20230814101223587.png)
明文不可能加密回自己本身,思路来了:只需要判断从哪个位置开始,二十个密文的子串和 WETTERBERICHT
的重合率为 0 即可。
举个例子就是,假设 pos=0
,那么二十个密文的第一个字符都不会是 W
,第二个字符都不会是 E
。
1 | from pwn import * |
bombe-Rejewski
题目说明:
德军目前使用日密钥加密临时密钥两次的工作模式,相信你一定可以发现其中的端倪!
注:enigma机可使用pyenigma项目,但源项目有问题,可修改后使用cyberchef对拍。
注:本题update了以下部分
- 修改了积分逻辑,统计密钥恢复正确的正确次数;
- 修改了挑战次数,提升一次打通的概率;
- 增加了日接线板,现在无法使用爆破的方式直接破拆;
还是看到 handle 部分
1 | def handle(self): |
看一下题目流程,
- 指定一个 3 字节的
daykey
- 生成三个打乱的字母表
k1,k2,k3
- 将每个字母表相同位置的字母取出来组合,生成 26 个 3 字节的随机字符串
tmpkeys
- 对每个随机字符串加密两次,输出密文。(根据恩格玛机的加密特性,本质上是加密了一串重复的字符串)
没有思路,找巨人的肩膀,根据 Rejewski
,
搜到了论文 https://people.cs.uchicago.edu/~davidcash/284-autumn-19/02-permutations-and-enigma.pdf
# Rejewski’s Theorem and Attack
在github上搜到了项目 https://github.com/NationalSecurityAgency/enigma-simulator/tree/master
简单读了下论文,了解了一下攻击流程。然后就可以跟着项目里的 MasterEnigmaCracker.ipynb
做了。不过需要调一下恩格玛机的参数。要把 rotor 和 Reflector 调成题目里的。
在 enigma-simulator-master/components.py
中设置
1 | ROTOR_WIRINGS = { |
其中 backword 的计算方式在题目给的 pyenigma/rotor 中有
1 | if wiring != None: |
然后还要在 component 中调一下 reflector
1 | class Reflector: |
另外在调 rejewski.py 中的 make_chain_length_dict() 函数生成字典的时候,根据直接把 rotors 的顺序定死
1 | for key in tqdm(keys): |
因为题目取得就是前三个转子 r1,r2,r3=myrotors[:3]
另外可能会出现多解的情况,我们无法判断,随便传一个就好;也有可能无解,那就随便猜一个。剩下的全靠运气了。我这里运气不错,第二次就成功了。
attack.py
1 | import machine as m |
inter.py
1 | import signal |
recover
题目描述:
小A发现一段纯P盒加密的密文,但等待他还原的其实是……?
(flag格式为flag{纯小写字母},对应变量本身即包含flag{}结构)
flag变量给出了最终提交内容的具体约束,具体格式为flag{xxx}中间为18个小写字母,约束为对含有“flag{}”整体的约束。readable是可读,不只是printable可打印。请各位师傅自行估算复杂度,正解基于python的爆破时间在5min左右,请观察加密部件特征,降低复杂度
1 | from math import ceil |
题目流程,,不总结了,一看全是线性运算,思路来了:上 z3,加密照抄
1 | from z3 import * |
然后发现原来我们需要恢复 key,那就继续 z3
1 | from Crypto.Util.number import * |
随后发现有多解了,而 z3 又无法给出所有的解,于是我就开始猜了。
猜key中包含哪些字符,首先根据题目名,我猜回包含字符串 recover
,但不确定 recover
的位置,那就爆 !
1 | from z3 import * |
得到 b'flag{ynrdertqrecoveryoe}'
,还不是正确答案,不过看到 recover 的位置,最后还剩 3 个字节,而且还有一个 e
在最后,忙猜是一个 key
,于是再加三条约束
1 | sol.add(keys_sym[2][4]== ord('k')) |
跑一下脚本,bingo!
1 | b'flag{hardertorecoverkey}' |
估摸着出题人的预期应该不是这么做,可以赛后看一下官方 wp 学习一下。
SecureAgg
交互题,看到 handle
1 | def handle(self): |
看一下题目流程,一共二十轮,每一轮
题目会给出一个
enc_list
,然后使用
agg.aggregate()
生成一个密钥key
下面会用这个
key
作为 AES 密钥加密一段随机字符串,要求我们解密这个随机字符串
轮与轮之间都是相互独立的,没啥操作空间,就是很单纯的要根据 enc_list
来恢复 key
定位到 enc_list
和 key
相关的代码,在AggServer.py
1 | def get_enc_list(self): |
所以 enc_list
就是每个用户的 enc_data
组成列表,key
就是每个用户的 data
之和。
我们继续看到 enc_data
相关的代码,在User.py
1 | def get_enc_data(self,M): |
其中 PRG 就是一个 LCG 伪随机数生成器,k 就是每个用户的自己的私钥和其他用户公钥生成的类似 DH 交换密钥的值
1 | def agree(self,users,p): |
id 是每个用户的id值(0-7)。所以思路来了:将 enc_data 求和减去 514*8 ,再在模 M 下除以 114 就可以了
举个例子, 在 get_enc_data
中有用户 A 和 B,A 的 id 小于 B,所以 A 的 enc 会减一个 $PRG((g^{b})^a)$,但是 B 的 id 大约 A,所以 B 的 enc 会加一个 $PRG((g^a)^b)$,是相等的,所以全加在一起的话就都消掉了。
1 | from pwn import * |
simple_des
1 | from operator import add |
题目实现了一个经典的 DES ,不过除了第一组,后面两组加密时密钥会先和前一组的明文异或。题目还给出了第一组加密时的最后一轮轮密钥( L 部分少了 9 比特,可以爆)
拿出当初祥哥的板子 https://blog.soreatu.com/posts/intended-solution-to-crypto-problems-in-nctf-2019/#reverse909pt-2solvers ,爆一下 L 少的 9 比特就可以了
1 | from base64 import b64decode |
得到第一组明文:NepCTF{N
,顺便输出了第一轮的轮密钥 combined
。
然后拿着第一组的明文 NepCTF{N
,做一个密钥拓展那边的 PC_1 置换
,再左右两边各自向左循环移位一下,再异或输出的第一轮的轮密钥 combined
,就可以解密第二组密文了,以此类推。
1 | from functools import reduce |
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com
文章标题:2023 NepCTF
文章字数:8.3k
本文作者:Van1sh
发布时间:2023-08-14, 12:00:00
最后更新:2023-08-14, 20:45:14
原始链接:http://jayxv.github.io/2023/08/14/2023 NepCTF/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。